/*******************************************************************************************
 *      @name   udp_broadcast.c
 *      @brief  这是在linux系统下的使用UDP套接字实现广播的代码案例，主要学习socket的属性的设置
 *      @date   2025/02/19
 *      @author cecelmx@126.com 
 *      @note
 *              1.想要使用UDP套接字通信的前提是调用socket函数创建UDP套接字
 *                udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
 * 
 *              2.UDP协议提供的是面向无连接、不可靠、固定长度的服务，无法确保数据包的交付
 * 
 *              3.接收方必须调用bind函数把UDP套接字和主机信息(端口/IP地址)进行绑定
 * 
 *              4.发送方需要调用sendto()函数发送数据，接收方需要调用recvfrom()函数接收数据
 * 
 *              5.由于UDP是面向无连接的服务，所以UDP套接字才具有广播功能
 *              
 *              6.实现UDP的广播功能，则需要对UDP套接字的options进行设置  SO_BROADCAST 广播选项
 * 
 *              7.需要调用getsockopt()/setsockopt()对套接字的选项进行获取/设置
 * 
 *      @version  xx.xx.xx   主版本号.次版本号.修正版本号
 * 
 *      CopyRight (c)   2024-2025   Your Name     All Right Reserved
 * 
 * ****************************************************************************************/

#include <stdio.h>

#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>


#define BUF_SIZE  1024  //接收缓冲区的大小

char recvbuf[BUF_SIZE]; //接收缓冲区
int udp_socket; //UDP套接字的文件描述符


#define MYPORT  8888 //本主机的端口

struct sockaddr_in src ={0};    //用于记录发送方主机的信息(端口和IP地址)
socklen_t srclen = sizeof(src);

//接收线程的任务
void *recv_handler(void *arg)
{
    for(;;)
    {
        //接收数据，未收到数据时会阻塞
        recvfrom(udp_socket,recvbuf,sizeof(recvbuf),0,(struct sockaddr*)&src,&srclen);

        //输出数据
        printf("recv data from [%s] is [%s]\n",inet_ntoa(src.sin_addr),recvbuf);
    }
}


int main(int argc,char **argv)
{

    char sendbuf[BUF_SIZE] ={0}; //用于存储待发送的数据

    //1.创建UDP套接字
    udp_socket = socket(AF_INET, SOCK_DGRAM, 0); 
    if(-1 == udp_socket)
    {
        fprintf(stderr,"create udp socket error,%s\n",strerror(errno));
        exit(-1);
    }
    
    //2.使能UDP套接字的广播属性
    int optval = 1;
    int ret = setsockopt(udp_socket,SOL_SOCKET,SO_BROADCAST,(void *)&optval,(socklen_t)sizeof(optval));
    if(-1 == ret)
    {
        fprintf(stderr,"setsockopt error,%s\n",strerror(errno));
        exit(-2);
    }

    //2.bind()是可选项，如果客户端需要接收来自其他主机的消息，则应该调用
    struct sockaddr_in myaddr ={0};
    myaddr.sin_family = AF_INET;                    //IPV4
    myaddr.sin_port   = htons(MYPORT);              //本地端口，需要转换为网络字节序
    myaddr.sin_addr.s_addr = INADDR_ANY;            //本地地址，需要转换为网络字节序
    bind(udp_socket,(struct sockaddr *)&myaddr,sizeof(myaddr));

    //3.创建一条接收线程，专门用于接收其他主机的消息
    pthread_t thread;
    pthread_create(&thread,NULL,recv_handler,NULL);

    //4.死循环

    struct sockaddr_in dest ={0};
    dest.sin_family = AF_INET;                              //IPV4
    dest.sin_port   = htons(8888);                          //目标端口，需要转换为网络字节序
    dest.sin_addr.s_addr = inet_addr("192.168.60.255");     //目标地址，需要转换为网络字节序

    for(;;)
    {
       
        //获取数据
        printf("请输入数据:");
        scanf("%s",sendbuf);

        //发送数据
        sendto(udp_socket,sendbuf,BUF_SIZE,0,(struct sockaddr *)&dest,sizeof(dest));
        printf("send OK\n");
    }

    //5.关闭套接字
    close(udp_socket);

    return 0;
}